今天我們接續前面的問題,大家是否有找到解答了呢?
如果沒有的話,我會在下面提供兩種做法:
第一種,在 memo 裡面用 callback 告訴它從哪裡比較:
import React, { memo, useCallback, useMemo, useState } from "react";
// 子層接 props 參數,觸發渲染
const Swatch = ({ params }) => {
console.log(`Swatch 渲染 ${params.color}`);
return (
<div
style={{
margin: "1rem",
width: 75,
height: 75,
borderRadius: "50%",
backgroundColor: params.color
}}
></div>
);
};
// 第一種解決的方法,直接告訴他比較機制
const MemoSwatch = memo(Swatch, (prevProps, nextProps) => {
return prevProps.params.color === nextProps.params.color;
});
// 母層
const MemoExample = () => {
const [appRenderIdx, setAppRenderIdx] = useState(0);
const [clr, setClr] = useState("blue");
console.log(`Memo 渲染次數 ${appRenderIdx}`);
return (
<div>
<h4>React Memo 範例</h4>
<div className="f-b-c">
<button onClick={() => setAppRenderIdx(appRenderIdx + 1)}>
重新渲染
</button>
<button
onClick={() => (clr === "blue" ? setClr("red") : setClr("blue"))}
>
換顏色
</button>
</div>
<MemoSwatch params={{ color: clr }} />
</div>
);
};
export default MemoExample;
第二種,使用 useMemo
hook 去處理 params:
import React, { memo, useCallback, useMemo, useState } from "react";
// 子層接 props 參數,觸發渲染
const Swatch = ({ params }) => {
console.log(`Swatch 渲染 ${params.color}`);
return (
<div
style={{
margin: "1rem",
width: 75,
height: 75,
borderRadius: "50%",
backgroundColor: params.color
}}
></div>
);
};
const MemoSwatch = memo(Swatch);
// 母層
const MemoExample = () => {
const [appRenderIdx, setAppRenderIdx] = useState(0);
const [clr, setClr] = useState("blue");
console.log(`Memo 渲染次數 ${appRenderIdx}`);
// 第二種解決辦法,使用useMemo處理帶入的參數
// 一樣是帶有dependency array所以當代入職參數變更時會告訴外層需要重新渲染
const params = useMemo(() => ({ color: clr }), [clr]);
return (
<div>
<h4>React Memo 範例</h4>
<div className="f-b-c">
<button onClick={() => setAppRenderIdx(appRenderIdx + 1)}>
重新渲染
</button>
<button
onClick={() => (clr === "blue" ? setClr("red") : setClr("blue"))}
>
換顏色
</button>
</div>
<MemoSwatch params={params} />
</div>
);
};
export default MemoExample;
那麼你的畫面應該就又回到正常了:
當按下重新渲染,子層不會被觸發了:
按下換顏色才會觸發:
那麼既然都提到 useMemo
了,我們就順便連 useCallback
的使用也一併講一講,在先前 useEffect
的章節也講解了 dependencies array的作用。
那麼在 useMemo
你可以單純理解為回吐資料型別為物件或陣列,也就是依照你 dependencies array 填入的資料判斷是否需要更新資料。
useCallback
的部分也是一樣的概念,只是這次針對的對象為函數(function),下面我們舉個沒用 useCallback
的例子吧!
import React, { useState } from 'react';
// 母層
const ParentComponent = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<ChildComponent onClick={increment} />
<p>Count: {count}</p>
</div>
);
}
// 子層
const ChildComponent = ({ onClick }) => {
console.log('我被渲染啦');
return <button onClick={onClick}>Increment</button>;
}
export default ParentComponent;
假設我們有一個母組件和一個子組件,子組件接一個 onClick callback 作為 props,當母組件重新渲染時,每次都會重新創這個 increment
函數給子組件去接 onClick callback;
這會導致子組件在母組件重新渲染時不必要地重做一次,因為對子組件而言,永遠是收到了一個新的 refference。
那麼,我們使用 useCallback
來優化這個情況:
import React, { useState, useCallback } from 'react';
// 母層
const ParentComponent = () => {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []);
return (
<div>
<ChildComponent onClick={increment} />
<p>Count: {count}</p>
</div>
);
}
// 子層不變
const ChildComponent = ({ onClick }) => {
console.log('我被渲染啦');
return <button onClick={onClick}>Increment</button>;
}
export default ParentComponent;
當母組件重新渲染時,由於我們使用了 useCallback
,increment
函數只會在 dependencies array 發生變化時才重新建立。在這個情況下,我們的 increment
函數的是 dependencies array 空陣列 []
,這代表它不會在母組件的重新渲染中重新建立,來達到減少了子組件的不必要重新渲染。
那麼今天的內容就到這裡,下一篇我們來認識 useMemo
和 useCallback
的使用上都該注意哪些問題。